//
//  NSArrayAdditions.m
//  Tint
//
//  Created by Kevin Wu on 2/22/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "NSArrayAdditions.h"
#import "NSObjectAdditions.h"

@implementation NSArray (Tint)


#pragma mark - Querying

- (id)objectOrNilAtIndex:(NSUInteger)i
{
    if ( i < [self count] ) {
        return [self objectAtIndex:i];
    }
    return nil;
}

- (id)firstObject
{
    return [self objectOrNilAtIndex:0];
}

- (id)randomObject
{
    if ( [self count] > 0 ) {
        int i = arc4random() % [self count];
        return [self objectAtIndex:i];
    }
    return nil;
}

- (BOOL)hasObjectEqualTo:(id)object
{
    return ( [self indexOfObject:object] != NSNotFound );
}

- (BOOL)hasObjectIdenticalTo:(id)object
{
    return ( [self indexOfObjectIdenticalTo:object] != NSNotFound );
}



#pragma mark - KVC

- (NSArray *)newArrayWithUnionOfValues:(NSString *)keyPath
{
    NSMutableArray *values = nil;
    if ( [self count] > 0 ) {
        values = [[NSMutableArray alloc] init];
        for ( int i=0; i<[self count]; ++i ) {
            id object = [self objectAtIndex:i];
            [values addObjectIfNotNil:[object valueForKeyPath:keyPath]];
        }
    }
    return values;
}

- (NSArray *)newArrayWithObjectsForKeyPath:(NSString *)keyPath equalToValue:(id)value
{
    NSMutableArray *objects = nil;
    if ( [self count] > 0 ) {
        objects = [[NSMutableArray alloc] init];
        for ( int i=0; i<[self count]; ++i ) {
            id object = [self objectAtIndex:i];
            if ( [object isValueForKeyPath:keyPath equalToValue:value] ) {
                [objects addObject:object];
            }
        }
    }
    return objects;
}

- (NSArray *)newArrayWithObjectsForKeyPath:(NSString *)keyPath identicalToValue:(id)value
{
    NSMutableArray *objects = nil;
    if ( [self count] > 0 ) {
        objects = [[NSMutableArray alloc] init];
        for ( int i=0; i<[self count]; ++i ) {
            id object = [self objectAtIndex:i];
            if ( [object isValueForKeyPath:keyPath identicalToValue:value] ) {
                [objects addObject:object];
            }
        }
    }
    return objects;
}

- (id)firstObjectForKeyPath:(NSString *)keyPath equalToValue:(id)value
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        if ( [object isValueForKeyPath:keyPath equalToValue:value] ) {
            return object;
        }
    }
    return nil;
}

- (id)firstObjectForKeyPath:(NSString *)keyPath identicalToValue:(id)value
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        if ( [object isValueForKeyPath:keyPath identicalToValue:value] ) {
            return object;
        }
    }
    return nil;
}


#pragma mark - Sending messages

- (void)makeObjectsRunSelector:(SEL)selector
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        [object runSelector:selector];
    }
}

- (void)makeObjectsRunSelector:(SEL)selector 
                    withObject:(id)p1
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        [object runSelector:selector 
                 withObject:p1];
    }
}

- (void)makeObjectsRunSelector:(SEL)selector 
                    withObject:(id)p1 
                    withObject:(id)p2
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        [object runSelector:selector 
                 withObject:p1 
                 withObject:p2];
    }
}

- (void)makeObjectsRunSelector:(SEL)selector 
                    withObject:(id)p1 
                    withObject:(id)p2 
                    withObject:(id)p3
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        [object runSelector:selector 
                 withObject:p1 
                 withObject:p2 
                 withObject:p3];
    }
}

- (void)makeObjectsRunSelector:(SEL)selector 
                    withObject:(id)p1 
                    withObject:(id)p2 
                    withObject:(id)p3 
                    withObject:(id)p4
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        [object runSelector:selector 
                 withObject:p1 
                 withObject:p2 
                 withObject:p3 
                 withObject:p4];
    }
}

- (void)makeObjectsRunSelector:(SEL)selector 
                    withObject:(id)p1 
                    withObject:(id)p2 
                    withObject:(id)p3 
                    withObject:(id)p4 
                    withObject:(id)p5
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        [object runSelector:selector 
                 withObject:p1 
                 withObject:p2 
                 withObject:p3 
                 withObject:p4 
                 withObject:p5];
    }
}

- (void)makeObjectsRunSelector:(SEL)selector 
                    withObject:(id)p1 
                    withObject:(id)p2 
                    withObject:(id)p3 
                    withObject:(id)p4 
                    withObject:(id)p5 
                    withObject:(id)p6
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        [object runSelector:selector 
                 withObject:p1 
                 withObject:p2 
                 withObject:p3 
                 withObject:p4 
                 withObject:p5 
                 withObject:p6];
    }
}



- (void)makeObjectsRunSelectorOnMainThread:(SEL)selector
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        [object runSelectorOnMainThread:selector];
    }
}

- (void)makeObjectsRunSelectorOnMainThread:(SEL)selector 
                                withObject:(id)p1
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        [object runSelectorOnMainThread:selector 
                             withObject:p1];
    }
}

- (void)makeObjectsRunSelectorOnMainThread:(SEL)selector 
                                withObject:(id)p1 
                                withObject:(id)p2
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        [object runSelectorOnMainThread:selector 
                             withObject:p1 
                             withObject:p2];
    }
}

- (void)makeObjectsRunSelectorOnMainThread:(SEL)selector 
                                withObject:(id)p1 
                                withObject:(id)p2 
                                withObject:(id)p3
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        [object runSelectorOnMainThread:selector 
                             withObject:p1 
                             withObject:p2 
                             withObject:p3];
    }
}

- (void)makeObjectsRunSelectorOnMainThread:(SEL)selector 
                                withObject:(id)p1 
                                withObject:(id)p2 
                                withObject:(id)p3 
                                withObject:(id)p4
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        [object runSelectorOnMainThread:selector 
                             withObject:p1 
                             withObject:p2 
                             withObject:p3 
                             withObject:p4];
    }
}

- (void)makeObjectsRunSelectorOnMainThread:(SEL)selector 
                                withObject:(id)p1 
                                withObject:(id)p2 
                                withObject:(id)p3 
                                withObject:(id)p4 
                                withObject:(id)p5
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        [object runSelectorOnMainThread:selector 
                             withObject:p1 
                             withObject:p2 
                             withObject:p3 
                             withObject:p4 
                             withObject:p5];
    }
}

- (void)makeObjectsRunSelectorOnMainThread:(SEL)selector 
                                withObject:(id)p1 
                                withObject:(id)p2 
                                withObject:(id)p3 
                                withObject:(id)p4 
                                withObject:(id)p5 
                                withObject:(id)p6
{
    for ( int i=0; i<[self count]; ++i ) {
        id object = [self objectAtIndex:i];
        [object runSelectorOnMainThread:selector 
                             withObject:p1 
                             withObject:p2 
                             withObject:p3 
                             withObject:p4 
                             withObject:p5 
                             withObject:p6];
    }
}


@end




@implementation NSMutableArray (Tint)

#pragma mark - Adding and removing entries

- (id)addObjectIfNotNil:(id)object
{
    if ( object ) {
        [self addObject:object];
        return object;
    }
    return nil;
}

- (id)addUnequalObjectIfNotNil:(id)object
{
    if ((object) && 
        ([self indexOfObject:object] == NSNotFound))
    {
        [self addObject:object];
        return object;
    }
    return nil;
}

- (id)addUnidenticalObjectIfNotNil:(id)object
{
    if ((object) && 
        ([self indexOfObjectIdenticalTo:object] == NSNotFound))
    {
        [self addObject:object];
        return object;
    }
    return nil;
}

- (id)insertObject:(id)object atIndexIfNotNil:(NSUInteger)index
{
    if ((object) && 
        (index <= [self count]))
    {
        [self insertObject:object atIndex:index];
        return object;
    }
    return nil;
}

- (id)moveObjectAtIndex:(NSUInteger)index toIndex:(NSUInteger)toIndex
{
    if ((index != toIndex) && 
        (index < [self count]) && 
        (toIndex < [self count]))
    {
        id object = [self objectAtIndex:index];
        [object retain];
        [self removeObjectAtIndex:index];
        [self insertObject:object atIndex:toIndex];
        [object release];
        return [self objectAtIndex:toIndex];
    }
    return nil;
}

- (void)removeFirstObject
{
    if ( [self count] > 0 ) {
        [self removeObjectAtIndex:0];
    }
}



#pragma mark - Ordering and filtering

// http://en.wikipedia.org/wiki/Knuth_shuffle
- (void)shuffle
{
    for ( NSUInteger i=[self count]; i>1; i-- ) {
        
        NSUInteger m = 1;
        do {
            m <<= 1;
        } while ( m < i );
        
        NSUInteger j;
        do {
            j = arc4random() % m;
        } while ( j >= i );
        
        [self exchangeObjectAtIndex:(i - 1) withObjectAtIndex:j];
    }
}

- (void)reverse
{
	for ( int i=0; i<(floor([self count] / 2.0)); i++ ) {
		[self exchangeObjectAtIndex:i withObjectAtIndex:([self count] - (i + 1))];
    }
}

- (void)unequal
{
    if ( [self count] > 0 ) {
        NSArray *array = [[NSArray alloc] initWithArray:self];
        [self removeAllObjects];
        for ( int i=0; i<[array count]; ++i ) {
            id object = [array objectAtIndex:i];
            [self addUnequalObjectIfNotNil:object];
        }
        [array release];
    }
}

- (void)unidentical
{
    if ( [self count] > 0 ) {
        NSArray *array = [[NSArray alloc] initWithArray:self];
        [self removeAllObjects];
        for ( int i=0; i<[array count]; ++i ) {
            id object = [array objectAtIndex:i];
            [self addUnidenticalObjectIfNotNil:object];
        }
        [array release];
    }
}



#pragma mark - Stack

- (id)push:(id)object
{
    return [self addObjectIfNotNil:object];
}

- (id)pop
{
    if ( [self count] > 0 ) {
        id object = [self lastObject];
        [object retain];
        [self removeLastObject];
        return [object autorelease]; // AUTO
    }
    return nil;
}



#pragma mark - Queue

- (id)enqueue:(id)object
{
    return [self addObjectIfNotNil:object];
}

- (id)dequeue
{
    if ( [self count] > 0 ) {
        id object = [self firstObject];
        [object retain];
        [self removeFirstObject];
        return [object autorelease]; // AUTO
    }
    return nil;
}

@end


